"""
Decorators package.
"""
import functools
import inspect

def singleton(cls):
    cls.__new_original__ = cls.__new__

    @functools.wraps(cls.__new__)
    def singleton_new(cls, *args, **kw):
        it =  cls.__dict__.get('__it__')
        if it is not None:
            return it

        cls.__it__ = it = cls.__new_original__(cls, *args, **kw)
        it.__init_original__(*args, **kw)
        return it

    cls.__new__ = singleton_new
    cls.__init_original__ = cls.__init__
    cls.__init__ = object.__init__

    return cls

def extractFunc(func, ifNot = None):
    if isinstance(func, (staticmethod, classmethod)):
        return func.__func__
    if inspect.isfunction(func):
        return func
    return ifNot

def extractName(obj):
    if hasattr(obj, '__name__'):
        return obj.__name__
    return obj.__func__.__name__

def functor(func):
    @functools.wraps(func)
    def wrapper(*oldArgs, **oldKw):
        def functorWrapper(*args, **kw):
            newArgs = oldArgs + args # first old args, then new
            newKw   = oldKw.copy()
            newKw.update(kw)
            return func(*newArgs, **newKw)
        return functorWrapper
    return wrapper

def functor_append(func):
    @functools.wraps(func)
    def wrapper(*oldArgs, **oldKw):
        def functorWrapper(*args, **kw):
            newArgs = args + oldArgs # first new args, then old
            newKw   = oldKw.copy()
            newKw.update(kw)
            return func(*newArgs, **newKw)
        return functorWrapper
    return wrapper

def decorator(func):
    """
    Support C# or Java attribute-like usage. After applying this decorator user's decorator could be used
    either with arguments or not. In case of arguments there will be created a functor to hold arguments.
    Otherwise decorator will be applyed derectly to function or class.

    Example with function:
    @decorator
    def my_decor(func, name = None):
        pass

    Example with class:
    @decorator
    class my_decor:
        def __init__(self, name = None):
            self.name = name

        def __call__(self, func):
            pass

    Usage in both cases:
    @my_decor           # use default args, brackets could be omitted
    def foo():
        pass

    @my_decor('name')   # store arguments and pass it to the end of arglist
    def foo():
        pass
    """
    def isFuncArg(*args, **kw):
        return len(args) == 1 and len(kw) == 0 and (
            inspect.isfunction(args[0]) or isinstance(args[0], type))

    if isinstance(func, type):
        def class_wrapper(*args, **kw):
            if isFuncArg(*args, **kw):
                return func()(*args, **kw) # create class before usage
            return func(*args, **kw)
        class_wrapper.__name__ = func.__name__
        class_wrapper.__module__ = func.__module__
        return class_wrapper

    @functools.wraps(func)
    def func_wrapper(*args, **kw):
        if isFuncArg(*args, **kw):
            return func(*args, **kw)
        def functor(userFunc):
            return func(userFunc, *args, **kw)
        return functor

    return func_wrapper

@decorator
def apply(func, *args, **kw):
    return func(*args, **kw)

@functor_append
def method_of(func, cls = None, name = None):
    setattr(cls, name or extractName(func), func)
    return func

@functor_append
def static_method_of(func, cls, name = None):
    setattr(cls, name or extractName(func), staticmethod(func))
    return func

@functor_append
def class_method_of(func, cls, name = None):
    setattr(cls, name or extractName(func), classmethod(func))
    return func
